// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================

// MELCommand.cpp : Implementation of CMELCommand

#include "stdafx.h"
#include "MELCommand.h"
#include "Common.h"

/*
 * Sends a command to the Maya command port. Connects to the command port
 * if not already connected. The result returned by Maya is stored as a
 * string in the m_strReply member variable, to be potentially processed
 * later by the get_Result() member function.
 *
 * Parameters
 *   Command [in] Null-terminated string with the command.
 *
 * Return Value
 *   Returns S_OK if successful, or an error value otherwise.
 *
 * Remarks
 *   The return value from this method only indicates whether the command
 *   was successfully sent to the Maya command port. It does not indicate
 *   if the command was successfully executed by Maya.
 */
STDMETHODIMP CMELCommand::Execute(BSTR Command)
{
	m_strResult.Empty();

	HRESULT hr = Connect();
	if (FAILED(hr))
		return hr;

	// convert BSTR to ANSI
	CAtlString strCommand(Command);
	CW2A pszCommand(strCommand);

	// send command to Maya command port
	int err = send(m_socket, pszCommand, (int)strlen(pszCommand), 0);
	if (err == SOCKET_ERROR)
	{
		Disconnect();
		return AtlReportError(GetObjectCLSID(),
			L"Unable to send command to command port",
			IID_IMELCommand);
	}

	// wait for reply, store result as raw string
	char* recvBuf = new char[4096+1];
	int numBytes = recv(m_socket, recvBuf, 4096, 0);
	if (numBytes == SOCKET_ERROR)
	{
		delete[] recvBuf;
		Disconnect();
		return AtlReportError(GetObjectCLSID(),
			L"Unable to get reply from command port",
			IID_IMELCommand);
	}
	recvBuf[numBytes] = '\0'; // practising safe string usage
	CA2W pszRecv(recvBuf); // convert ANSI to Unicode
	m_strResult = pszRecv;
	delete[] recvBuf;
	m_strResult.TrimLeft(L" \r\n");  // trim junk from beginning
	m_strResult.TrimRight(L" \r\n"); // ...and end

	return S_OK;
}

/*
 * The Result property specifies the result returned by Maya for the most
 * recent invocation of the Execute method. This property is read-only.
 *
 * Parameters
 *   pVal [out] Returns the Result property as a reference to a VARIANT.
 *
 * Remarks
 *   The Result property can be a LONG (VT_I4), DOUBLE (VT_R8),
 *   BSTR (VT_BSTR), or an array (VT_ARRAY) of those types.
 */
STDMETHODIMP CMELCommand::get_Result(VARIANT* pVal)
{
	return MakeVariant(m_strResult).Detach(pVal);
}


/*
 * The PortName property specifies the name of the Maya command port.
 *
 * Parameters
 *   pVal   [out] Returns the PortName property as a reference to a BSTR.
 *   newVal [in]  Sets the value of the PortName property to the value of the BSTR.
 *
 * Remarks
 *   Changing the PortName property while a connection is established will
 *   close that connection.
 *   The default/initial value of the PortName property is "mayaCommand".
 */
STDMETHODIMP CMELCommand::get_PortName(BSTR* pVal)
{
	*pVal = m_strPortName.AllocSysString();
	return S_OK;
}
STDMETHODIMP CMELCommand::put_PortName(BSTR newVal)
{
	CAtlString strPortName(newVal);
	if (m_strPortName != strPortName)
	{
		Disconnect();
		m_strPortName = strPortName;
	}
	return S_OK;
}

/*
 * Connects to the Maya command port, if not already connected.
 *
 * Return Value
 *   Returns S_OK if successful or already connected,
 *   or an error value otherwise.
 */
STDMETHODIMP CMELCommand::Connect(void)
{
	if (m_socket != INVALID_SOCKET)
		// already connected!
		return S_OK;

	// initialize Windows Sockets 2
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 0), &wsaData) == 0)
	{
		CW2A pszPortName(m_strPortName);
		m_socket = ConnectToMayaCommandPortByName(pszPortName);
		if (m_socket != INVALID_SOCKET)
			return S_OK;
	}
	WSACleanup();

	CAtlString strError = L"Unable to connect to command port: ";
	strError += m_strPortName;
	return AtlReportError(GetObjectCLSID(), strError, IID_IMELCommand);
}

/*
 * Closes the connection to the Maya command port;
 * does nothing if not connected.
 *
 * Return Value
 *   Returns S_OK.
 */
STDMETHODIMP CMELCommand::Disconnect(void)
{
	if (m_socket != INVALID_SOCKET)
	{
		DisconnectFromMayaCommandPort(m_socket);
		m_socket = INVALID_SOCKET;
		WSACleanup();
	}
	return S_OK;
}

/*
 * The Connected property specifies whether there currently is a connection
 * established with the Maya command port. This property is read-only.
 *
 * Parameters
 *   pVal [out] Returns the whether a connection currently exists.
 */
STDMETHODIMP CMELCommand::get_Connected(VARIANT_BOOL* pVal)
{
	*pVal = (m_socket == INVALID_SOCKET) ? FALSE : TRUE;
	return S_OK;
}

/*
 * Converts the specified (reply) string into a Variant value.
 * If the string contains tab characters ('\t), the string is
 * considered to contain an array of values.
 */
CComVariant CMELCommand::MakeVariant(const CAtlString& strText)
{
	int tabCount = 0;
	int tabPos = 0;
	while (tabPos < strText.GetLength()) {
		tabPos = strText.Find(L'\t', tabPos);
		if (tabPos < 0)
			break;
		++tabCount;
		++tabPos;
	}
	if (!tabCount) {
		return MakeVariantNotArray(strText);
	}
	CComSafeArray<VARIANT> safeArray;
	safeArray.Create(tabCount + 1);
	int curPos = 0;
	int index = 0;
	tabPos = strText.Find(L'\t');
	for (;;) {
		CAtlString strElement;
		if (tabPos > curPos) {
			strElement = strText.Mid(curPos, tabPos - curPos).Trim();
		}
		safeArray[index++] = MakeVariantNotArray(strElement);
		curPos = tabPos + 1;
		if (curPos == strText.GetLength()) {
			safeArray[index] = CComVariant(L"");
			break;
		}
		tabPos = strText.Find(L'\t', curPos);
		if (tabPos < 0) {
			safeArray[index] = MakeVariant(strText.Mid(curPos).Trim());
			break;
		}
	}
	return CComVariant(safeArray);
}

/*
 * Converts the specified (sub-)string into a Variant value.
 */
CComVariant CMELCommand::MakeVariantNotArray(const CAtlString& strText)
{
	CComVariant variant(strText);
	if (strText.SpanExcluding(L"0123456789.-+e").IsEmpty()) {
		if (strText.Find(L".") < 0) {
			variant.ChangeType(VT_I4);
		} else {
			variant.ChangeType(VT_R8);
		}
	}
	return variant;
}

